package com.longge.bigfile.service.impl;

import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.OSS;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadResult;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.UploadFileRequest;
import com.aliyun.oss.model.UploadFileResult;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.longge.bigfile.config.OSSConfiguration.OSSConfig;
import com.longge.bigfile.dto.request.BaseDto;
import com.longge.bigfile.util.OSSClientUtils;

import lombok.extern.slf4j.Slf4j;

/**
 * 存储类型（Storage Class）
	OSS 提供标准、低频访问、归档三种存储类型，全面覆盖从热到冷的各种数据存储场景。其中标准存储类型提供高可靠、高可用、高性能的对象存储服务，能够支持频繁的数据访问；
	低频访问存储类型适合长期保存不经常访问的数据（平均每月访问频率 1 到 2 次），存储单价低于标准类型；归档存储类型适合需要长期保存（建议半年以上）的归档数据，在三种存储类型中单价最低。详情请参见存储类型介绍。
	
	存储空间（Bucket）
	存储空间是您用于存储对象（Object）的容器，所有的对象都必须隶属于某个存储空间。存储空间具有各种配置属性，包括地域、访问权限、存储类型等。您可以根据实际需求，创建不同类型的存储空间来存储不同的数据。创建存储空间请参见创建存储空间。
	
	对象/文件（Object）
	对象是 OSS 存储数据的基本单元，也被称为 OSS 的文件。对象由元信息（Object Meta）、用户数据（Data）和文件名（Key）组成。对象由存储空间内部唯一的 Key 来标识。
	对象元信息是一组键值对，表示了对象的一些属性，比如最后修改时间、大小等信息，同时您也可以在元信息中存储一些自定义的信息。
	
	地域（Region）
	地域表示 OSS 的数据中心所在物理位置。您可以根据费用、请求来源等选择合适的地域创建 Bucket。详情请参见 OSS 已开通的Region。
	
	访问域名（Endpoint）
	Endpoint 表示 OSS 对外服务的访问域名。OSS 以 HTTP RESTful API 的形式对外提供服务，当访问不同地域的时候，需要不同的域名。
	通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个 Region 对应的 Endpoint。
	
	访问密钥（AccessKey）
	AccessKey（简称 AK）指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。OSS 通过使用 AccessKeyId 和 AccessKeySecret 对称加密的方法来验证某个请求的发送者身份。
	AccessKeyId 用于标识用户；AccessKeySecret 是用户用于加密签名字符串和 OSS 用来验证签名字符串的密钥，必须保密。获取 AccessKey 的方法请参见创建 AccessKey。
	
 * @author yangzhilong
 *
 */
@Slf4j
public class OSSProcessServiceImpl extends AbstractProcessServiceImpl {
	private static ThreadLocal<OSS> clientLocal = new ThreadLocal<>();
    private static ThreadLocal<OSSConfig> configLocal = new ThreadLocal<>();
	
	@Override
	public void setThreadLocal(BaseDto dto) {
		configLocal.set(OSSClientUtils.getConfig(dto.getSys()));
		clientLocal.set(OSSClientUtils.getClient(dto.getSys()));
	}

	@Override
	public void removeThreadLocal() {
		configLocal.remove();
		clientLocal.remove();
	}
	
	@Override
	public String initUpload(String realFileKey) {
		InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(configLocal.get().getBucketName(), realFileKey);
		InitiateMultipartUploadResult initResult = clientLocal.get().initiateMultipartUpload(initRequest);
		return initResult.getUploadId();
	}
	
	@Override
	public String uploadRealFile(MultipartFile file, String realFileKey)  {
		try(InputStream is = file.getInputStream();) {
			UploadFileRequest uploadFileRequest = new UploadFileRequest(configLocal.get().getBucketName(), realFileKey);
			UploadFileResult result = clientLocal.get().uploadFile(uploadFileRequest);
			if(null != result) {
				return result.getMultipartUploadResult().getETag();
			}
		} catch (Throwable e) {
			log.error("upload real file to oss error", e);
		} 
		return null;
	}
	
	@Override
	public String uploadPartFile(MultipartFile file, String realFileKey, String uploadId, int sliceIndex) {
		UploadPartRequest uploadRequest = new UploadPartRequest();
		
		uploadRequest.setUploadId(uploadId);
		uploadRequest.setBucketName(configLocal.get().getBucketName());
		uploadRequest.setKey(realFileKey);
		// 设置分片号。每一个上传的分片都有一个分片号，取值范围是1~10000
		uploadRequest.setPartNumber(sliceIndex+1);
		try(InputStream is = file.getInputStream();) {
			uploadRequest.setInputStream(is);
			UploadPartResult uploadResult = clientLocal.get().uploadPart(uploadRequest);
			
			PartETag eTag = uploadResult.getPartETag();
			// 由于OSS的eTag需要一个com.aliyun.oss.model.PartETag对象，故返回值将对象json序列化后返回
			if(null != eTag) {
				return JSONObject.toJSONString(eTag);
			}
		} catch (Exception e) {
			log.error("upload part file to oss error", e);
		}
		return null;
	}

	@Override
	public String completedUpload(String uploadId, String realFileKey, Set<String> endETags) {
		 List<PartETag> partETags = endETags.stream().map(item -> JSONObject.parseObject(item, PartETag.class)).collect(Collectors.toList());
		CompleteMultipartUploadRequest completeRequest =
		        new CompleteMultipartUploadRequest(configLocal.get().getBucketName(), realFileKey, uploadId, partETags);
		CompleteMultipartUploadResult completeResult = clientLocal.get().completeMultipartUpload(completeRequest);
		if(null != completeResult) {
			return completeResult.getETag();
		}
		return null;
	}
}
